/*
 * Javlov - a Java toolkit for reinforcement learning with multi-agent support.
 * 
 * Copyright (c) 2009 Matthijs Snel
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.javlov.world.ui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JPanel;

import net.javlov.world.AgentBody;
import net.javlov.world.Body;
import net.javlov.world.World;


public class WorldView extends JPanel implements Observer, MouseWheelListener, ActionListener {
	
	private static final long serialVersionUID = -193232841584505904L;
	
	protected World model;
	protected List<BodyView> bodies;
	
	private AffineTransform scaleXform, saveXform;
	private double modelWidth, modelHeight;
	
	public WorldView(World w) {
		bodies = new ArrayList<BodyView>();
		setModel(w);
		addMouseWheelListener(this);
		scaleXform = new AffineTransform();
	}
	
	public void add(Body b) {
		//TODO use factory
		if ( b instanceof AgentBody )
			bodies.add(new AgentBodyView(b));
		else {
			bodies.add(new BodyView(b));
		}
	}
	
	public World getModel() {
		return model;
	}

	public void setModel(World model) {
		bodies.clear();
		List<Body> modelBodies = model.getObjects();
		for ( int i = 0; i < modelBodies.size(); i++ ) {
			add(modelBodies.get(i));
			//System.out.println(modelBodies.get(i).getType());
		}

		this.model = model;
		modelWidth = model.getWidth();
		modelHeight = model.getHeight();
		setPreferredSize(new Dimension((int)modelWidth, (int)modelHeight));

	}
	
	/**
	 * Just invokes paintImmediately to repaint the whole world. Override if anything
	 * more fancy should be going on, e.g. redrawing only the areas that have changed, which
	 * makes sense for worlds with few moving objects.
	 */
	public void draw() {
		paintImmediately(0, 0, getWidth(), getHeight());
	}
	
	@Override
	protected void paintComponent(Graphics g) {
		//TODO add possibility to draw joints and contacts
		super.paintComponent(g);
		Graphics2D g2d = (Graphics2D) g;
		saveXform = g2d.getTransform();
		g2d.transform(scaleXform);
		g2d.setColor(Color.white);
		g2d.fillRect(0, 0, (int)modelWidth, (int)modelHeight);
		g2d.setColor(Color.black);
		for ( BodyView view : bodies ) {
			view.draw(g);
		}
		
		g2d.setTransform(saveXform);
	}

	@Override
	public void update(Observable o, Object arg) {
		//a body was added to the simulation
		if ( arg != null && arg instanceof Body )
			add((Body)arg);
	}

	@Override
	public void mouseWheelMoved(MouseWheelEvent e) {
		scaleXform.scale(1 - e.getWheelRotation()*0.05, 1 - e.getWheelRotation()*0.05);
		repaint();
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		draw();
	}
	
	/**
	 * Only call this from within paintComponent, otherwise transform may be an old one
	 * @return
	 */
	protected AffineTransform getOriginalTransform() {
		return saveXform;
	}
	
	/**
	 * Only call this from within paintComponent, otherwise transform may be an old one
	 * @return
	 */
	protected AffineTransform getScaleTransform() {
		return scaleXform;
	}
}